Release 10.1A: OpenEdge Getting Started:
Object-oriented Programming


Using delegation with classes

Object-oriented design provides multiple models for facilitating code reuse. One model is inheritance, which is the model shown in most of the examples thus far. The other code reuse model is the delegation model. This section demonstrates how you can use the delegation model in Progress. In the delegation model one class acts as a principal object for a set of related behavior that is implemented by other classes. This principal object is called a container class. The container class can create instances of other classes with the NEW statement. These other classes that provide services to the container are called delegate classes. The container class executes delegate class behavior by forwarding method invocations (object messages) on to the delegate for processing, using the object-reference:method-name syntax. In this way, the principal class acts as a container for aggregated behavior in the sense that it is responsible for starting, managing, and using the other classes that provide services to the container. Each delegate class can be used by multiple containers to provide the same services for different purposes.

The association between a container class and a delegate class is somewhat looser than the strict compile-time definition of a class hierarchy, in which each class in the hierarchy explicitly states its position within the hierarchy as its first statement. There is no statement at the top of a class defining it to be a container or a delegate. However, note that the relationships and dependencies between a container and its delegates are verified and enforced at compile-time, and that the compiler references the code for each delegate of a container in order to validate all the method calls to the delegate from the container. This is a key part of the value of strong typing in the class model. Since any variable that holds an object reference must be defined explicitly for that class type, the compiler knows exactly what methods and data members can be accessed through that reference.

If the container class allows other classes to run behavior in its delegates through the container, the container must define a stub method, exposed to other classes, for each method that is, in fact, implemented by the delegate. In other words, the fact that a container class uses behavior in one of its delegates does not make the delegate behavior available to yet another class unless the container exposes it through a method of its own.

The following example demonstrates delegation. In the example, only the container is shown. The behavior of the delegates is not represented. The container class and both delegate classes called logToDB and logToFile must provide an implementation of the methods openLog( ), writeLog( ), and closeLog( ). The natural mechanism to enforce this design is the use of an interface. The following code shows the example interface definition:

INTERFACE Ilog: 
    METHOD PUBLIC VOID openLog (INPUT name AS CHARACTER). 
    METHOD PUBLIC VOID writeLog (INPUT txt AS CHARACTER). 
    METHOD PUBLIC VOID closeLog ( ). 
END INTERFACE. 

The container class itself implements the Ilog interface. As you can see from the following sample code, the container's version of these methods invokes the same method in one or the other of its two delegates, which are created by the constructor. The container also implements a separate setMode( ) method to specify the delegate behavior to use, which is initially logToFile. For example:

CLASS container IMPLEMENTS Ilog: 
    DEFINE PRIVATE VARIABLE rlogToDB   AS CLASS logToDB   NO-UNDO. 
    DEFINE PRIVATE VARIABLE rlogToFile AS CLASS logToFile NO-UNDO. 
    /* logMode = 1 (File) is the default */ 
    DEFINE PRIVATE VARIABLE logMode    AS INTEGER INITIAL 1 NO-UNDO.  
    CONSTRUCTOR PUBLIC container ( ): 
        rlogToDB = NEW logToDB ( ). 
        rlogToFile = NEW logToFile ( ). 
    END CONSTRUCTOR. 
    METHOD PUBLIC VOID setMode (INPUT ilogMode AS INTEGER) 
        logMode = ilogMode. 
    END METHOD. 
    METHOD PUBLIC VOID openLog (INPUT name AS CHARACTER): 
        IF logMode EQ 1 THEN 
            rlogToFile:openLog (INPUT name). 
        ELSE 
            rlogToDB:openLog (INPUT name). 
    END METHOD. 
    METHOD PUBLIC VOID writeLog (INPUT txt AS CHARACTER): 
        IF logMode EQ 1 THEN 
            rlogToFile:writeLog (INPUT txt). 
        ELSE 
            rlogToDB:writeLog (INPUT txt). 
    END METHOD. 
    METHOD PUBLIC VOID closeLog ( ): 
        IF logMode EQ 1 THEN 
            rlogToFile:closeLog ( ). 
        ELSE 
            rlogToDB:closeLog ( ). 
    END METHOD. 
    DESTRUCTOR PUBLIC container ( ): 
        DELETE OBJECT rlogToDB NO-ERROR. 
        rlogToDB = ?. 
        DELETE OBJECT rlogToFile NO-ERROR. 
        rlogToFile = ?. 
    END METHOD. 
END CLASS. 

Comparison with procedure-based programming

The use of containers and delegates in classes is very different from procedures, in which a RUN statement for another procedure or for an internal procedure in a handle cannot be checked until run time. The unanticipated run-time error that occurs when one procedure mistakenly runs another becomes instead a compile-time error when you use classes that can be corrected before the application is ever executed.


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095